#include "Nano100Series.h"
#include "Common.h"
#include "I2c.h"
#include "GpioI2c.h"

/*
 * refer to I2C specification
 */

static void GpioI2cStart(struct gpio_i2c_handle_t *gpio_i2c_handle,
								uint32_t period_half)
{
	gpio_i2c_handle->op_set_scl(GPIO_I2C_LEVEL_H);
	gpio_i2c_handle->op_set_sda(GPIO_I2C_LEVEL_H);
	gpio_i2c_handle->op_set_sda(GPIO_I2C_LEVEL_L);
	DelayUs(period_half);
}

static void GpioI2cStop(struct gpio_i2c_handle_t *gpio_i2c_handle,
								uint32_t period_half)
{
	gpio_i2c_handle->op_set_scl(GPIO_I2C_LEVEL_L);
	gpio_i2c_handle->op_set_sda(GPIO_I2C_LEVEL_L);
	DelayUs(period_half);
	gpio_i2c_handle->op_set_scl(GPIO_I2C_LEVEL_H);
	DelayUs(period_half);
	gpio_i2c_handle->op_set_sda(GPIO_I2C_LEVEL_H);
	DelayUs(period_half);
}

static void GpioI2cRestart(struct gpio_i2c_handle_t *gpio_i2c_handle,
								uint32_t period_half)
{
	gpio_i2c_handle->op_set_scl(GPIO_I2C_LEVEL_L);
	gpio_i2c_handle->op_set_sda(GPIO_I2C_LEVEL_H);
	DelayUs(period_half);
	gpio_i2c_handle->op_set_scl(GPIO_I2C_LEVEL_H);
	DelayUs(period_half);
	gpio_i2c_handle->op_set_sda(GPIO_I2C_LEVEL_L);
	DelayUs(period_half);
}

static uint8_t GpioI2cWriteByte(struct gpio_i2c_handle_t *gpio_i2c_handle,
								uint8_t mbyte, uint32_t period_half)
{
	int i;
	uint8_t mbit;

	for (i = 7; i >= 0; i--) {
		gpio_i2c_handle->op_set_scl(GPIO_I2C_LEVEL_L);
		gpio_i2c_handle->op_set_sda(((mbyte >> i) & 0x01) ? GPIO_I2C_LEVEL_H : GPIO_I2C_LEVEL_L);
		DelayUs(period_half);
		gpio_i2c_handle->op_set_scl(GPIO_I2C_LEVEL_H);
		DelayUs(period_half);
	}
	/* read ack */
	gpio_i2c_handle->op_set_scl(GPIO_I2C_LEVEL_L);
	gpio_i2c_handle->op_set_sda(GPIO_I2C_LEVEL_H);	// release sda, after scl low
	DelayUs(period_half);
	gpio_i2c_handle->op_set_scl(GPIO_I2C_LEVEL_H);
	mbit = gpio_i2c_handle->op_get_sda();			// read sda, after scl high
	DelayUs(period_half);

	return mbit;
}

static void GpioI2cReadByte(struct gpio_i2c_handle_t *gpio_i2c_handle,
								uint8_t *mbyte, uint32_t period_half, enum gpio_i2c_level_t ack)
{
	int i;
	uint8_t mbit;

	*mbyte = 0;
	for (i = 7; i >= 0; i--) {
		gpio_i2c_handle->op_set_scl(GPIO_I2C_LEVEL_L);
		gpio_i2c_handle->op_set_sda(GPIO_I2C_LEVEL_H);	// release sda, after scl low
		DelayUs(period_half);
		gpio_i2c_handle->op_set_scl(GPIO_I2C_LEVEL_H);
		mbit = gpio_i2c_handle->op_get_sda();			// read sda, after scl high
		DelayUs(period_half);
		*mbyte = *mbyte | (mbit << i);
	}
	/* write ack */
	gpio_i2c_handle->op_set_scl(GPIO_I2C_LEVEL_L);
	gpio_i2c_handle->op_set_sda(ack);
	DelayUs(period_half);
	gpio_i2c_handle->op_set_scl(GPIO_I2C_LEVEL_H);
	DelayUs(period_half);
}

static uint32_t GpioI2cSpeedToPeriod(uint32_t speed)
{
	uint32_t period;

	period = 1000 * 1000 * 10 / speed;	 // scale 10
	if (period < 20) {
		period = 20; 					// min 2us, then 500KHz
	}
	if ((period % 20) >= 10) {
		period = (period / 20 + 1) * 20;
	} else {
		period = (period / 20) * 20;
	}
	period = period / 10;

	return period;
}

void GpioI2cInit(struct gpio_i2c_handle_t *gpio_i2c_handle,
				GpioI2cOpSetScl op_set_scl,
				GpioI2cOpGetScl op_get_scl,
				GpioI2cOpSetSda op_set_sda,
				GpioI2cOpGetSda op_get_sda,
				uint32_t speed)
{
	gpio_i2c_handle->op_set_scl = op_set_scl;
	gpio_i2c_handle->op_get_scl = op_get_scl;
	gpio_i2c_handle->op_set_sda = op_set_sda;
	gpio_i2c_handle->op_get_sda = op_get_sda;
	gpio_i2c_handle->speed = speed;

	gpio_i2c_handle->op_set_scl(GPIO_I2C_LEVEL_H);
	gpio_i2c_handle->op_set_sda(GPIO_I2C_LEVEL_H);
}

int GpioI2cXfer(struct gpio_i2c_handle_t *gpio_i2c_handle,
				struct i2c_msg_t *i2c_msg)
{
	uint32_t period;
	uint32_t period_half;
	uint8_t mbyte;
	uint8_t mbit;
	int i;
	int ret = 0;

	period = GpioI2cSpeedToPeriod(gpio_i2c_handle->speed);
	period_half = period / 2;

	// start
	GpioI2cStart(gpio_i2c_handle, period_half);

	// addr
	mbyte = i2c_msg->addr << 1;
	mbit = GpioI2cWriteByte(gpio_i2c_handle, mbyte, period_half);
	if (mbit) {
		ret = -1;
		goto stop;
	}

	// write
	for (i = 0; i < i2c_msg->tx_to_write; i++) {
		mbyte = i2c_msg->tx_buf[i];
		mbit = GpioI2cWriteByte(gpio_i2c_handle, mbyte, period_half);
		if (mbit) {
			ret = -1;
			goto stop;
		}
		i2c_msg->tx_write++;
	}

	if (i2c_msg->rx_to_read == 0) {
		ret = 0;
		goto stop;
	}

	// restart
	GpioI2cRestart(gpio_i2c_handle, period_half);

	// addr
	mbyte = (i2c_msg->addr << 1) | 0x01;
	mbit = GpioI2cWriteByte(gpio_i2c_handle, mbyte, period_half);
	if (mbit) {
		ret = -1;
		goto stop;
	}

	// read
	for (i = 0; i < (i2c_msg->rx_to_read - 1); i++) {
		GpioI2cReadByte(gpio_i2c_handle, &mbyte, period_half, GPIO_I2C_LEVEL_L);		// ack
		i2c_msg->rx_buf[i] = mbyte;
		i2c_msg->rx_read++;
	}
	GpioI2cReadByte(gpio_i2c_handle, &mbyte, period_half, GPIO_I2C_LEVEL_H);		// nack
	i2c_msg->rx_buf[i] = mbyte;
	i2c_msg->rx_read++;

stop:
	// stop
	GpioI2cStop(gpio_i2c_handle, period_half);

	return ret;
}
